/*
 *  $Id: Chain.cs 1233 2010-12-18 17:54:48Z thenn.erannor $
 *  This file is a part of Ragima CCG.
 *  (C) 2008-2010 Ragima development group.
 *
 *  Licensed under the terms of the GNU GPL License version 2.
 *
 */

using System;
using System.Collections.Generic;

using Nereal.Serialization;

namespace Ragima {
    /// <summary>
    /// Предикат проверки возможности определенного набора целей.
    /// </summary>
    public delegate bool ChainTargetPredicate(TargetArgs<ChainData> args);

    /// <summary>
    /// Тип цели: несколько последовательных целей.
    /// </summary>
    public class ChainTarget : TargetType {
        private List<TargetType> _targets;
        private List<TargetArgs> _args;
        private ChainTargetPredicate _condition;
        private int _current;

        public ChainTarget(ChainTargetPredicate condition) : base() {
            _targets = new List<TargetType>();
            _args = new List<TargetArgs>();
            _condition = condition;
            _current = 0;
        }
        public ChainTarget(ChainTargetPredicate condition, params TargetType[] targets) : this(condition) {
            _targets.AddRange(targets);
        }

        /// <summary>
        /// Закончилась ли последовательность целей.
        /// </summary>
        public bool EndChain {
            get { return _current >= _targets.Count; }
        }
        /// <summary>
        /// Текущая цель в последовательности.
        /// </summary>
        public TargetType Current {
            get { return EndChain ? this : _targets[_current]; }
        }

        /// <summary>
        /// Выбирает следующую цель в последовательности или завершает её.
        /// </summary>
        public void NextTarget() {
            _current++;
            if (EndChain) {
                var args = new ChainTargetArgs(Request.Player, Request.Caster, new ChainData(_args));
                _args.Clear();
                if (_condition != null && !_condition(args)) {
                    Game.Current.ResetTarget();
                    return;
                }
                Request.Execute(args);
            } else {
                UpdateTarget();
            }
        }

        /// <summary>
        /// Выбирает предыдущую цель в последовательности.
        /// </summary>
        public void PrevTarget() {
            if (_current == 0)
                return;
            _current--;
            _args.RemoveAt(_current);
            UpdateTarget();
        }

        /// <summary>
        /// Обновляет состояние игры на текущую цель.
        /// </summary>
        private void UpdateTarget() {
            UpdateNotice();
            Current.Initialize(Request);
            Current.AutoSelectTab();
            Game.Current.UpdateTargetNotice();
            Game.Current.UpdateRegions();
        }

        /// <summary>
        /// Обновляет подсказку цели.
        /// </summary>
        private void UpdateNotice() {
            Notice = Current.Notice;
        }

        public override bool IsRealTarget {
            get { return EndChain ? base.IsRealTarget : Current.IsRealTarget; }
        }

        /// <summary>
        /// Инициализирует последовательность и выбирает первую цель в ней.
        /// </summary>
        public override void Initialize(TargetRequest request) {
            base.Initialize(request);
            _args.Clear();
            _current = 0;
            if (!EndChain) {
                UpdateNotice();
                Current.Initialize(request);
            }
        }

        public override void Cancel() {
            if (_current > 0)
                PrevTarget();
            else
                base.Cancel();
        }

        protected override void AutoSelectTab(Player player) {
            if (!EndChain)
                Current.AutoSelectTab();
        }

        public override bool HasTargets(AbstractObject caster) {
            foreach (var target in _targets)
                if (!target.HasTargets(caster))
                    return false;
            return true;
        }

        public override int GetCount(AbstractObject caster) {
            return EndChain ? base.GetCount(caster) : Current.GetCount(caster);
        }

        public override IEnumerable<Region> GetRegions() {
            return EndChain ? base.GetRegions() : Current.GetRegions();
        }

        public override void ClickRegion(Region region) {
            HandleRegion(region);
        }

        public override bool AcceptRegion(Region region) {
            return EndChain ? base.AcceptRegion(region) : Current.AcceptRegion(region);
        }

        public override void ClickAcceptedRegion(Region region) {
            HandleRegion(region);
        }

        private void HandleRegion(Region region) {
            var args = Current.GetArgs(region);
            _args.Add(args);
            NextTarget();
        }
    }

    [TypeId("chain")]
    public class ChainTargetArgs : TargetArgs<ChainData> {
        [Serialize, XmlName("target")]
        private ChainData _target;

        public ChainTargetArgs() {
            _target = new ChainData();
        }
        public ChainTargetArgs(Player player, AbstractObject caster, ChainData target) : base(player, caster) {
            _target = target;
        }

        public override ChainData Target {
            get { return _target; }
        }
    }

    public class ChainData {
        [XmlName(null, "args")]
        public readonly List<TargetArgs> Args;

        public ChainData() {
            Args = new List<TargetArgs>();
        }
        public ChainData(IEnumerable<TargetArgs> args) {
            Args = new List<TargetArgs>(args);
        }

        /// <summary>
        /// Получает цель определенного типа по указанному индексу в последовательности.
        /// </summary>
        public bool GetArgs<T>(int index, out TargetArgs<T> args) {
            args = null;
            if (index >= 0 && index < Args.Count)
                args = Args[index] as TargetArgs<T>;
            return args != null;
        }

        /// <summary>
        /// Получает цель определенного типа из последовательности длиной 1.
        /// </summary>
        public bool GetArgs<T1>(out TargetArgs<T1> args1) {
            args1 = null;
            if (Args.Count == 1)
                args1 = Args[0] as TargetArgs<T1>;
            return args1 != null;
        }
        /// <summary>
        /// Получает цели определенных типов из последовательности длиной 2.
        /// </summary>
        public bool GetArgs<T1, T2>(out TargetArgs<T1> args1, out TargetArgs<T2> args2) {
            args1 = null;
            args2 = null;
            if (Args.Count == 2) {
                args1 = Args[0] as TargetArgs<T1>;
                args2 = Args[1] as TargetArgs<T2>;
            }
            return args1 != null && args2 != null;
        }
        /// <summary>
        /// Получает цели определенных типов из последовательности длиной 3.
        /// </summary>
        public bool GetArgs<T1, T2, T3>(out TargetArgs<T1> args1, out TargetArgs<T2> args2, out TargetArgs<T3> args3) {
            args1 = null;
            args2 = null;
            args3 = null;
            if (Args.Count == 3) {
                args1 = Args[0] as TargetArgs<T1>;
                args2 = Args[1] as TargetArgs<T2>;
                args3 = Args[2] as TargetArgs<T3>;
            }
            return args1 != null && args2 != null && args3 != null;
        }
    }
}
